package output

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"chainmaker.org/chainmaker/smarteditor/common"
	"chainmaker.org/chainmaker/smarteditor/conf"
	"chainmaker.org/chainmaker/smarteditor/session"
	"chainmaker.org/chainmaker/smarteditor/util"
	"github.com/88250/gulu"
	"github.com/jinzhu/gorm"
)

type deployContract struct {
	Name      string `json:"name,omitempty"`
	State     string `json:"state,omitempty"`
	Path      string `json:"path,omitempty"`
	BuildTime string `json:"buildTime"`
}

type hasDeployContractListResponse struct {
	Date    string   `json:"date,omitempty"`
	Name    string   `json:"name,omitempty"`
	State   string   `json:"state,omitempty"`
	Methods []string `json:"methods,omitempty"`
}

type HasDeployContractListResponse struct {
	Total         int                             `json:"total"`
	HasDeployList []hasDeployContractListResponse `json:"hasDeployList"`
}

const (
	Compiled   = "1"
	UnCompiled = "2"

	Deployed   = "1"
	UnDeployed = "2"
)

type DeployContractListInfo struct {
	Total      int              `json:"total"`
	DeployList []deployContract `json:"deployList"`
}

// //DeployContractListHandler get deploy contract list
// func DeployContractListHandler(w http.ResponseWriter, r *http.Request) {
// 	uid, err := session.GetUidFromSession(r)
// 	if err != nil {
// 		log.Errorf("CrossCompilationHandler Forbidden. url:%s, uid:%s, err:%v", r.URL, uid, err)
// 		http.Error(w, "Forbidden", http.StatusForbidden)
// 		return
// 	}

// 	result := gulu.Ret.NewResult()
// 	defer gulu.Ret.RetResult(w, r, result)

// 	buildFileDir := conf.GetBuildDir(conf.GetUserWorkspace(uid))

// 	contractDirList, err := ioutil.ReadDir(buildFileDir)
// 	if err != nil {
// 		result.Code = -1
// 		result.Msg = "server error"
// 		log.Errorf("[DeployContractListHandler] read dir fail. err: %s", err)
// 	}

// 	deployContractList := make([]deployContract, 0)
// 	for _, contractDir := range contractDirList {
// 		if contractDir.IsDir() {
// 			contractAbsDir := filepath.Join(buildFileDir, contractDir.Name())
// 			filePath7z, err := get7zFileFromDir(contractAbsDir)
// 			if err != nil {
// 				log.Warnf("[DeployContractListHandler] get 7z filefail. dir:%s,err: %s", contractAbsDir, err)
// 				continue
// 			}

// 			stat, err := os.Stat(filePath7z)
// 			if err != nil {
// 				log.Warnf("[DeployContractListHandler] get 7z file stat failaure. file:%s,err: %s", filePath7z, err)
// 				continue
// 			}

// 			deployContractList = append(deployContractList, deployContract{
// 				Name:      contractDir.Name(),
// 				State:     Compiled,
// 				Path:      filepath.ToSlash(filePath7z),
// 				BuildTime: stat.ModTime().Format(common.TimeLayout),
// 			})
// 		}
// 	}
// 	result.Data = deployContractList
// }

// DeployContractListHandler get deploy contract list
func DeployContractListHandler(w http.ResponseWriter, r *http.Request) {
	uid, err := session.GetUidFromSession(r)
	if err != nil {
		log.Warnf("DeployContractListHandler Forbidden. url:%s, uid:%s, err:%v", r.URL, uid, err)
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)
	// 解析page 和 last_ts
	var args map[string]interface{}
	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
		log.Warnf("DeployContractListHandler decode parameter failed. url:%s, err:%v", r.URL, err)
		result.Code = -1
		result.Msg = "invalid param"
		return
	}
	pageO, ok := args["page"]
	page := int64(1)
	if ok {
		page, _ = pageO.(int64)
		if page <= 0 {
			page = 1
		}
	}
	pageSizeO, ok := args["pageSize"]
	pageSize := int64(-1)
	if ok {
		pageSize, _ = pageSizeO.(int64)
		if pageSize <= 0 {
			pageSize = -1
		}
	}

	queryList, total, queryErr := conf.GetCompileList(uid, page, pageSize)
	if queryErr != nil {
		result.Code = -2 // 服务内部错误
		result.Msg = common.ErrorInternal
		return
	}
	var retData DeployContractListInfo
	retData.Total = total
	var deployList []deployContract
	for i := 0; i < len(queryList); i++ {
		tempDeploy := deployContract{
			Name:      queryList[i].ContractName,
			State:     Compiled,
			Path:      queryList[i].Path,
			BuildTime: time.Unix(queryList[i].BuildTime, 0).Format(common.TimeLayout),
		}
		deployList = append(deployList, tempDeploy)
	}
	retData.DeployList = deployList
	result.Data = retData
}

// //DeployHandler append deploy info to deploy list
// func DeployHandler(w http.ResponseWriter, r *http.Request) {
// 	uid, err := session.GetUidFromSession(r)
// 	if err != nil {
// 		log.Errorf("DeployHandler Forbidden. url:%s, uid:%s, err:%v", r.URL, uid, err)
// 		http.Error(w, "Forbidden", http.StatusForbidden)
// 		return
// 	}

// 	result := gulu.Ret.NewResult()
// 	defer gulu.Ret.RetResult(w, r, result)
// 	var args map[string]interface{}
// 	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
// 		log.Errorf("DeployHandler decode parameter failed. url:%s, err:%v", r.URL, err)
// 		result.Code = -1
// 		return
// 	}

// 	contractName := args["contractName"].(string)
// 	if contractName == "" {
// 		http.Error(w, "parameter error", http.StatusBadRequest)
// 		return
// 	}

// 	workspace := conf.GetUserWorkspace(uid)
// 	buildFile := filepath.Join(conf.GetBuildDir(workspace), contractName, conf.GetBuildInfoName())
// 	if !util.FileExists(buildFile) {
// 		log.Errorf("DeployHandler contract not found. url:%s, err:%v", r.URL, err)
// 		http.Error(w, "contract not found", http.StatusInternalServerError)
// 		return
// 	}
// 	deployFile := filepath.Join(conf.GetDeployDir(workspace), conf.GetDeployInfoName())
// 	log.Debugf("DeployHandler copy build info. build info file:%s, deploy info file:%s", buildFile, deployFile)

// 	buildContractInfo, err := readBuildInfo(buildFile)
// 	if err != nil {
// 		log.Errorf("DeployHandler read build info. build info file:%s, err：%s", buildFile, err)
// 		result.Code = -1
// 		result.Msg = "read data error"
// 		return
// 	}

// 	deployContractInfo, err := readDeployInfo(deployFile)
// 	if err != nil {
// 		log.Errorf("DeployHandler read deploy info. deploy info file:%s, err:%s", deployFile, err)
// 		result.Code = -1
// 		result.Msg = "read data error"
// 		return
// 	}

// 	exists := false
// 	for i := range deployContractInfo {
// 		if deployContractInfo[i].BuildInfo.ContractName == buildContractInfo.ContractName {
// 			deployContractInfo[i].Date = time.Now().Format(common.TimeLayout)
// 			deployContractInfo[i].BuildInfo = buildContractInfo
// 			exists = true
// 			break
// 		}
// 	}
// 	if !exists {
// 		deployContractInfo = append(deployContractInfo, deployInfo{
// 			Date:      time.Now().Format(common.TimeLayout),
// 			BuildInfo: buildContractInfo,
// 		})
// 	}

// 	err = writeDeployInfo(deployFile, deployContractInfo)
// 	if err != nil {
// 		log.Errorf("DeployHandler write deploy info. deploy info file:%s, err:%s", deployFile, err)
// 		result.Code = -1
// 		result.Msg = "write data error"
// 		return
// 	}
// }

// DeployHandler append deploy info to deploy list
func DeployHandler(w http.ResponseWriter, r *http.Request) {
	uid, err := session.GetUidFromSession(r)
	if err != nil {
		log.Errorf("DeployHandler Forbidden. url:%s, uid:%s, err:%v", r.URL, uid, err)
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}
	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)
	var args map[string]interface{}
	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
		log.Errorf("DeployHandler decode parameter failed. url:%s, err:%v", r.URL, err)
		result.Code = -1
		return
	}
	contractName := args["contractName"].(string)
	if contractName == "" {
		http.Error(w, "parameter error", http.StatusBadRequest)
		return
	}
	workspace := conf.GetUserWorkspace(uid)
	contractDeployDir := conf.GetDeployContractDir(workspace, contractName)
	// 查build-info中的表数据
	compileInfo, compileQueryErr := conf.GetCompileInfo(uid, contractName)
	if compileQueryErr != nil {
		if gorm.IsRecordNotFoundError(compileQueryErr) {
			http.Error(w, "contract not found", http.StatusInternalServerError)
			return
		}
		log.Errorf("DeployHandler uid %s GetCompileInfo failed %s", uid, compileQueryErr.Error())
		http.Error(w, common.ErrorInternal, http.StatusInternalServerError)
	}
	// 检查一下deploy下文件夹是否存在
	newDeployDir := filepath.Join(contractDeployDir, compileInfo.BinMd5)
	if !util.FileExists(newDeployDir) {
		log.Errorf("DeployHandler uid %s, compileInfo [%+v], deploy not exists", uid, *compileInfo)
		http.Error(w, common.ErrorInternal, http.StatusInternalServerError)
		return
	}
	// 保存deploy-info表中的数据,
	deployInfo := conf.ConstructDeployInfoByCompileInfo(compileInfo)
	saveErr := conf.SaveDeployInfo(deployInfo)
	if saveErr != nil {
		log.Errorf("DeployHandler uid %s , SaveDeployInfo [%+v] failed %s", uid, *deployInfo, saveErr.Error())
		http.Error(w, common.ErrorInternal, http.StatusInternalServerError)
		return
	}
	// 删除deploy下所有非hash的文件夹
	deleteDir, deleteDirErr := conf.RemoveAllSkip(contractDeployDir, []string{compileInfo.BinMd5})
	if deleteDirErr != nil {
		log.Errorf("DeployHandler uid %s , RemoveAllSkip failed %s", uid, deleteDirErr.Error())
	}
	log.Infof("DeployHandler uid %s , RemoveAllSkip %+v", uid, deleteDir)
	return
}

// //DeployHandler append deploy info to deploy list
// func HasDeployContractListHandler(w http.ResponseWriter, r *http.Request) {
// 	uid, err := session.GetUidFromSession(r)
// 	if err != nil {
// 		log.Errorf("DeployHandler Forbidden. url:%s, uid:%s, err:%v", r.URL, uid, err)
// 		http.Error(w, "Forbidden", http.StatusForbidden)
// 		return
// 	}

// 	result := gulu.Ret.NewResult()
// 	defer gulu.Ret.RetResult(w, r, result)
// 	deployFile := filepath.Join(conf.GetDeployDir(conf.GetUserWorkspace(uid)), conf.GetDeployInfoName())
// 	deployContractInfo, err := readDeployInfo(deployFile)
// 	if err != nil {
// 		log.Errorf("DeployHandler read deploy info. deploy info file:%s, err:%s", deployFile, err)
// 		result.Code = -1
// 		result.Msg = "read data error"
// 		return
// 	}

// 	response := make([]hasDeployContractListResponse, 0)
// 	for i := range deployContractInfo {
// 		response = append(response, hasDeployContractListResponse{
// 			Date:    deployContractInfo[i].Date,
// 			Name:    deployContractInfo[i].BuildInfo.ContractName,
// 			State:   Deployed,
// 			Methods: deployContractInfo[i].BuildInfo.Methods,
// 		})
// 	}

// 	result.Code = 0
// 	result.Data = response
// 	return
// }

// DeployHandler append deploy info to deploy list
func HasDeployContractListHandler(w http.ResponseWriter, r *http.Request) {
	uid, err := session.GetUidFromSession(r)
	if err != nil {
		log.Errorf("DeployHandler Forbidden. url:%s, uid:%s, err:%v", r.URL, uid, err)
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)

	// 解析page 和 last_ts
	var args map[string]interface{}
	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
		log.Warnf("HasDeployContractListHandler decode parameter failed. url:%s, err:%v", r.URL, err)
		result.Code = -1
		result.Msg = "invalid param"
		return
	}
	pageO, ok := args["page"]
	page := int64(1)
	if ok {
		page, _ = pageO.(int64)
		if page <= 0 {
			page = 1
		}
	}
	pageSizeO, ok := args["pageSize"]
	pageSize := int64(-1)
	if ok {
		pageSize, _ = pageSizeO.(int64)
		if pageSize <= 0 {
			pageSize = -1
		}
	}

	dbList, total, dbErr := conf.GetDeployList(uid, page, pageSize)
	if dbErr != nil {
		log.Errorf("HasDeployContractListHandler userId %s failed %s", uid, dbErr.Error())
		result.Code = -1
		result.Msg = common.ErrorInternal
		return
	}
	var retData HasDeployContractListResponse
	retData.Total = total
	var retList []hasDeployContractListResponse
	for i := 0; i < len(dbList); i++ {
		retList = append(retList, hasDeployContractListResponse{
			Date:    time.Unix(dbList[i].DeployTime, 0).Format(common.TimeLayout),
			Name:    dbList[i].ContractName,
			State:   Deployed,
			Methods: dbList[i].Methods,
		})
	}
	retData.HasDeployList = retList
	result.Data = retData
	return
}

func get7zFileFromDir(dir string) (string, error) {
	fileStats, err := ioutil.ReadDir(dir)
	if err != nil {
		return "", err
	}
	for _, fileStat := range fileStats {
		if !fileStat.IsDir() && strings.HasSuffix(fileStat.Name(), ".7z") {
			return filepath.Join(dir, fileStat.Name()), nil
		}
	}
	return "", errors.New("7z file not found")
}

// func readBuildInfo(buildInfoPath string) (*buildInfo, error) {
// 	buildInfoData, err := ioutil.ReadFile(buildInfoPath)
// 	if err != nil {
// 		return nil, err
// 	}
// 	result := buildInfo{}
// 	err = json.Unmarshal(buildInfoData, &result)
// 	return &result, err
// }

// func readDeployInfo(deployInfoPath string) ([]deployInfo, error) {
// 	deployInfoList := make([]deployInfo, 0)
// 	if !util.FileExists(deployInfoPath) {
// 		return deployInfoList, nil
// 	}

// 	deployInfoData, err := ioutil.ReadFile(deployInfoPath)
// 	if err != nil {
// 		return nil, err
// 	}
// 	if len(deployInfoData) == 0 {
// 		return deployInfoList, nil
// 	}
// 	err = json.Unmarshal(deployInfoData, &deployInfoList)
// 	return deployInfoList, err
// }

// func writeDeployInfo(deployFile string, deployInfoList []deployInfo) error {
// 	marshal, err := json.Marshal(deployInfoList)
// 	if err != nil {
// 		return err
// 	}
// 	err = ioutil.WriteFile(deployFile, marshal, 0644)
// 	return err
// }

// DeleteBuildHandler 删除编译信息
func DeleteBuildHandler(w http.ResponseWriter, r *http.Request) {
	uid, err := session.GetUidFromSession(r)
	if err != nil {
		log.Errorf("DeleteBuildHandler Forbidden. url:%s, uid:%s, err:%v", r.URL, uid, err)
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)
	var args map[string]interface{}
	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
		log.Errorf("DeleteBuildHandler decode parameter failed. url:%s, err:%v", r.URL, err)
		result.Code = -1
		result.Msg = common.ErrorInternal
		return
	}

	contractName := args["contractName"].(string)
	if contractName == "" {
		http.Error(w, "parameter error", http.StatusBadRequest)
		return
	}
	// 删除build-info表信息
	deleteErr := conf.DeleteCompileInfo(uid, contractName)
	if deleteErr != nil {
		log.Errorf("DeleteBuildHandler uid %s, contract %s,DeleteCompileInfo failed %s",
			uid, contractName, deleteErr.Error())
		result.Code = -1
		result.Msg = common.ErrorInternal
		return
	}
	// 删除build+contract目录下所有
	removeErr := os.RemoveAll(conf.GetBuildContractDir(conf.GetUserWorkspace(uid), contractName))
	if removeErr != nil {
		log.Errorf("DeleteBuildHandler uid %s, contract %s,RemoveAll failed %s",
			uid, contractName, removeErr.Error())
	}
}

// DeleteDeployHandler 删除部署信息
func DeleteDeployHandler(w http.ResponseWriter, r *http.Request) {
	uid, err := session.GetUidFromSession(r)
	if err != nil {
		log.Errorf("DeleteDeployHandler Forbidden. url:%s, uid:%s, err:%v", r.URL, uid, err)
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)
	var args map[string]interface{}
	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
		log.Errorf("DeleteDeployHandler decode parameter failed. url:%s, err:%v", r.URL, err)
		result.Code = -1
		result.Msg = common.ErrorInternal
		return
	}

	contractName := args["contractName"].(string)
	if contractName == "" {
		http.Error(w, "parameter error", http.StatusBadRequest)
		return
	}
	// 删除deploy-info表数据
	deleteErr := conf.DeleteDeployInfo(uid, contractName)
	if deleteErr != nil {
		log.Errorf("DeleteDeployHandler uid %s ,contract %s, DeleteDeployInfo failed %s", uid, contractName, deleteErr.Error())
		result.Code = -1
		result.Msg = common.ErrorInternal
		return
	}
	// 删除合约下所有的deploy信息
	removeErr := os.RemoveAll(conf.GetDeployContractDir(conf.GetUserWorkspace(uid), contractName))
	if removeErr != nil {
		log.Errorf("DeleteDeployHandler uid %s ,contract %s, RemoveAll failed %s", uid, contractName, removeErr.Error())
	}
	return
}
